// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
// For more information, see LICENCE in the main folder

#include "mmo.h"
#include "showmsg.h"
#include "malloc.h"
#include "core.h"
#include "strlib.h"
#ifndef MINICORE
#include "db.h"
#include "socket.h"
#include "timer.h"
#include "thread.h"
#include "mempool.h"
#include "sql.h"
#include "cbasetypes.h"
#include "msg_conf.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#ifndef _WIN32
#include <unistd.h>
#else
#include "../common/winapi.h" // Console close event handling
#include <direct.h>
#endif


/// Called when a terminate signal is received.
void (*shutdown_callback)(void) = NULL;

#if defined(BUILDBOT)
	int buildbotflag = 0;
#endif

int runflag = CORE_ST_RUN;
int arg_c = 0;
char **arg_v = NULL;

char *SERVER_NAME = NULL;
char SERVER_TYPE = ATHENA_SERVER_NONE;

#ifndef MINICORE	// minimalist Core
// Added by Gabuzomeu
//
// This is an implementation of signal() using sigaction() for portability.
// (sigaction() is POSIX; signal() is not.)  Taken from Stevens' _Advanced
// Programming in the UNIX Environment_.
//
#ifdef WIN32	// windows don't have SIGPIPE
#define SIGPIPE SIGINT
#endif

#ifndef POSIX
#define compat_signal(signo, func) signal(signo, func)
#else
sigfunc *compat_signal(int signo, sigfunc *func) {
	struct sigaction sact, oact;

	sact.sa_handler = func;
	sigemptyset(&sact.sa_mask);
	sact.sa_flags = 0;
#ifdef SA_INTERRUPT
	sact.sa_flags |= SA_INTERRUPT;	/* SunOS */
#endif

	if (sigaction(signo, &sact, &oact) < 0)
		return (SIG_ERR);

	return (oact.sa_handler);
}
#endif

/*======================================
 *	CORE : Console events for Windows
 *--------------------------------------*/
#ifdef _WIN32
static BOOL WINAPI console_handler(DWORD c_event) {
    switch(c_event) {
		case CTRL_CLOSE_EVENT:
		case CTRL_LOGOFF_EVENT:
		case CTRL_SHUTDOWN_EVENT:
			if( shutdown_callback != NULL )
				shutdown_callback();
			else
				runflag = CORE_ST_STOP;// auto-shutdown
			break;
		default:
			return FALSE;
		}
    return TRUE;
}

static void cevents_init() {
	if (SetConsoleCtrlHandler(console_handler,TRUE)==FALSE)
		ShowWarning ("Unable to install the console handler!\n");
}
#endif

/*======================================
 *	CORE : Signal Sub Function
 *--------------------------------------*/
static void sig_proc(int sn) {
	static int is_called = 0;

	switch (sn) {
		case SIGINT:
		case SIGTERM:
			if (++is_called > 3)
				exit(EXIT_SUCCESS);
			if( shutdown_callback != NULL )
				shutdown_callback();
			else
				runflag = CORE_ST_STOP;// auto-shutdown
			break;
		case SIGSEGV:
		case SIGFPE:
			do_abort();
			// Pass the signal to the system's default handler
			compat_signal(sn, SIG_DFL);
			raise(sn);
			break;
#ifndef _WIN32
		case SIGXFSZ:
			// ignore and allow it to set errno to EFBIG
			ShowWarning ("Max file size reached!\n");
			//run_flag = 0;	// should we quit?
			break;
		case SIGPIPE:
			//ShowInfo ("Broken pipe found... closing socket\n");	// set to eof in socket.c
			break;	// does nothing here
#endif
	}
}

void signals_init (void) {
	compat_signal(SIGTERM, sig_proc);
	compat_signal(SIGINT, sig_proc);
#ifndef _DEBUG // need unhandled exceptions to debug on Windows
	compat_signal(SIGSEGV, sig_proc);
	compat_signal(SIGFPE, sig_proc);
#endif
#ifndef _WIN32
	compat_signal(SIGILL, SIG_DFL);
	compat_signal(SIGXFSZ, sig_proc);
	compat_signal(SIGPIPE, sig_proc);
	compat_signal(SIGBUS, SIG_DFL);
	compat_signal(SIGTRAP, SIG_DFL);
#endif
}
#endif

#ifdef SVNVERSION
const char *get_svn_revision(void) {
	return EXPAND_AND_QUOTE(SVNVERSION);
}
#else // not SVNVERSION
const char* get_svn_revision(void) {
	static char svn_version_buffer[16] = "";
	FILE *fp;

	if( svn_version_buffer[0] != '\0' )
		return svn_version_buffer;

	// subversion 1.7 uses a sqlite3 database
	// FIXME this is hackish at best...
	// - Ignores database file structure
	// - Assumes the data in NODES.dav_cache column ends with "!svn/ver/<revision>/<path>)"
	// - Since it's a cache column, the data might not even exist
	if( (fp = fopen(".svn"PATHSEP_STR"wc.db", "rb")) != NULL || (fp = fopen(".."PATHSEP_STR".svn"PATHSEP_STR"wc.db", "rb")) != NULL ) {
#ifndef SVNNODEPATH
		//not sure how to handle branches, so i'll leave this overridable define until a better solution comes up
		#define SVNNODEPATH trunk
#endif
		const char* prefix = "!svn/ver/";
		const char* postfix = "/"EXPAND_AND_QUOTE(SVNNODEPATH)")"; // there should exist only 1 entry like this
		size_t prefix_len = strlen(prefix);
		size_t postfix_len = strlen(postfix);
		size_t i,j,len;
		char* buffer;

		// Read file to buffer
		fseek(fp, 0, SEEK_END);
		len = ftell(fp);
		buffer = (char*)aMalloc(len + 1);
		fseek(fp, 0, SEEK_SET);
		len = fread(buffer, 1, len, fp);
		buffer[len] = '\0';
		fclose(fp);

		// Parse buffer
		for( i = prefix_len + 1; i + postfix_len <= len; ++i ) {
			if( buffer[i] != postfix[0] || memcmp(buffer + i, postfix, postfix_len) != 0 )
				continue; // Postfix missmatch
			for( j = i; j > 0; --j ) { // Skip digits
				if( !ISDIGIT(buffer[j - 1]) )
					break;
			}
			if( memcmp(buffer + j - prefix_len, prefix, prefix_len) != 0 )
				continue; // Prefix missmatch
			// Done
			snprintf(svn_version_buffer, sizeof(svn_version_buffer), "%d", atoi(buffer + j));
			break;
		}
		aFree(buffer);

		if( svn_version_buffer[0] != '\0' )
			return svn_version_buffer;
	}

	// subversion 1.6 and older?
	if( (fp = fopen(".svn/entries", "r")) != NULL ) {
		char line[1024];
		int rev;

		// Check the version
		if( fgets(line, sizeof(line), fp) ) {
			if( !ISDIGIT(line[0]) ) {
				// XML File format
				while( fgets(line, sizeof(line), fp) )
					if( strstr(line, "revision=") ) break;
				if( sscanf(line," %*[^\"]\"%d%*[^\n]", &rev) == 1 )
					snprintf(svn_version_buffer, sizeof(svn_version_buffer), "%d", rev);
			} else {
				// Bin File format
				if( fgets(line, sizeof(line), fp) == NULL ) { printf("Can't get bin name\n"); } // Get the name
				if( fgets(line, sizeof(line), fp) == NULL ) { printf("Can't get entries kind\n"); } // Get the entries kind
				if( fgets(line, sizeof(line), fp) ) { // Get the rev numver
					snprintf(svn_version_buffer, sizeof(svn_version_buffer), "%d", atoi(line));
				}
			}
		}
		fclose(fp);

		if( svn_version_buffer[0] != '\0' )
			return svn_version_buffer;
	}

	// Fallback
	snprintf(svn_version_buffer, sizeof(svn_version_buffer), "Unknown");
	return svn_version_buffer;
}
#endif

/*======================================
 *	CORE : Display title
 *  ASCII By CalciumKid 1/12/2011
 *--------------------------------------*/
static void display_title(void) {
	//ClearScreen(); // clear screen and go up/left (0, 0 position in text)

	ShowMessage("\n");
	ShowMessage(""CL_PASS"        "CL_BOLD"                                                             "CL_PASS""CL_CLL""CL_NORMAL"\n");
	ShowMessage(""CL_PASS"        "CL_BT_WHITE"            Pengembangan idAthena mempersembahkan        "CL_PASS""CL_CLL""CL_NORMAL"\n");
	ShowMessage(""CL_PASS"        "CL_BOLD"       _      _     ___   __  __                             "CL_PASS""CL_CLL""CL_NORMAL"\n");
	ShowMessage(""CL_PASS"        "CL_BOLD"      |_| ___| |   /   | / /_/ /_  ___  ____  ____ _         "CL_PASS""CL_CLL""CL_NORMAL"\n");
	ShowMessage(""CL_PASS"        "CL_BOLD"      | ||  _  |  / /| |/ __/ __ \\/ _ \\/ __ \\/ __ `/      "CL_PASS""CL_CLL""CL_NORMAL"\n");
	ShowMessage(""CL_PASS"        "CL_BOLD"      | || |_| | / ___ / /_/ / / /  __/ / / / /_/ /          "CL_PASS""CL_CLL""CL_NORMAL"\n");
	ShowMessage(""CL_PASS"        "CL_BOLD"      |_||____/ /_/  |_\\__/_/ /_/\\___/_/ /_/\\__,_/        "CL_PASS""CL_CLL""CL_NORMAL"\n");
	ShowMessage(""CL_PASS"        "CL_BOLD"                                                             "CL_PASS""CL_CLL""CL_NORMAL"\n");  
	ShowMessage(""CL_PASS"        "CL_GREEN"               http://idAthena.org                          "CL_PASS""CL_CLL""CL_NORMAL"\n");
	ShowMessage(""CL_PASS"        "CL_BOLD"                                                             "CL_PASS""CL_CLL""CL_NORMAL"\n");

	ShowInfo("SVN Revision: '"CL_WHITE"%s"CL_RESET"'.\n", get_svn_revision());
}

// Warning if executed as superuser (root)
void usercheck(void)
{
#ifndef _WIN32
    if( geteuid() == 0 )
		ShowWarning ("You are running idAthena with root privileges, it is not necessary.\n");
#endif
}

/*======================================
 *	CORE : MAINROUTINE
 *--------------------------------------*/
int main (int argc, char **argv)
{
	{ // Initialize program arguments
		char *p1 = SERVER_NAME = argv[0];

		if( (p1 = strrchr(argv[0], '/')) != NULL || (p1 = strrchr(argv[0], '\\')) != NULL ) {
			char *pwd = NULL; // Path working directory
			int n = 0;

			SERVER_NAME = ++p1;
			n = p1 - argv[0]; // Calc dir name len
			pwd = safestrncpy(malloc(n + 1), argv[0], n);
			if( chdir(pwd) != 0 )
				ShowError("Couldn't change working directory to %s for %s, runtime will probably fail", pwd, SERVER_NAME);
			free(pwd);
		}

		arg_c = argc;
		arg_v = argv;

	}

	malloc_init(); // Needed for Show* in display_title() [FlavioJS]

#ifdef MINICORE // Minimalist Core
	display_title();
	usercheck();
	do_init(argc,argv);
	do_final();
#else// not MINICORE
	set_server_type();
	display_title();
	usercheck();

	Sql_init();
	rathread_init();
	mempool_init();
	db_init();
	signals_init();

#ifdef _WIN32
	cevents_init();
#endif

	timer_init();
	socket_init();

	do_init(argc,argv);

	// Main runtime cycle
	while (runflag != CORE_ST_STOP) {
		int next = do_timer(gettick_nocache());
		do_sockets(next);
	}

	do_final();

	timer_final();
	socket_final();
	db_final();
	mempool_final();
	rathread_final();
#endif

	malloc_final();

	return 0;
}
